﻿/*----------------------------------------------------------------
	文件名：WXBizMsgCrypt.cs
	文件功能描述：加解密算法
	
	
	创建标识：Senparc - 20150313
	
	修改标识：Senparc - 20150313
	修改描述：整理接口
----------------------------------------------------------------*/

using System;
using System.Collections;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
//using System.Web;

//-40001 ： 签名验证错误
//-40002 :  xml解析失败
//-40003 :  sha加密生成签名失败
//-40004 :  AESKey 非法
//-40005 :  corpid 校验错误
//-40006 :  AES 加密失败
//-40007 ： AES 解密失败
//-40008 ： 解密后得到的buffer非法
//-40009 :  base64加密异常
//-40010 :  base64解密异常
namespace Senparc.Weixin.QY.Tencent
{
   public class WXBizMsgCrypt
	{
		string m_sToken;
		string m_sEncodingAESKey;
		string m_sCorpID;
		enum WXBizMsgCryptErrorCode
		{
			WXBizMsgCrypt_OK = 0,
			WXBizMsgCrypt_ValidateSignature_Error = -40001,
			WXBizMsgCrypt_ParseXml_Error = -40002,
			WXBizMsgCrypt_ComputeSignature_Error = -40003,
			WXBizMsgCrypt_IllegalAesKey = -40004,
			WXBizMsgCrypt_ValidateCorpid_Error = -40005,
			WXBizMsgCrypt_EncryptAES_Error = -40006,
			WXBizMsgCrypt_DecryptAES_Error = -40007,
			WXBizMsgCrypt_IllegalBuffer = -40008,
			WXBizMsgCrypt_EncodeBase64_Error = -40009,
			WXBizMsgCrypt_DecodeBase64_Error = -40010
		};

		//构造函数
		// @param sToken: 公众平台上，开发者设置的Token
		// @param sEncodingAESKey: 公众平台上，开发者设置的EncodingAESKey
		// @param sCorpID: 企业号的CorpID
		public WXBizMsgCrypt(string sToken, string sEncodingAESKey, string sCorpID)
		{
			m_sToken = sToken;
			m_sCorpID = sCorpID;
			m_sEncodingAESKey = sEncodingAESKey;
		}

		//验证URL
		// @param sMsgSignature: 签名串，对应URL参数的msg_signature
		// @param sTimeStamp: 时间戳，对应URL参数的timestamp
		// @param sNonce: 随机串，对应URL参数的nonce
		// @param sEchoStr: 随机串，对应URL参数的echostr
		// @param sReplyEchoStr: 解密之后的echostr，当return返回0时有效
		// @return：成功0，失败返回对应的错误码
		public int VerifyURL(string sMsgSignature, string sTimeStamp, string sNonce, string sEchoStr, ref string sReplyEchoStr)
		{
			int ret = 0;
			if (m_sEncodingAESKey.Length!=43)
			{
				return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
			}
			ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEchoStr, sMsgSignature);
			if (0 != ret)
			{
				return ret;
			}
			sReplyEchoStr = "";
			string cpid = "";
			try
			{
				sReplyEchoStr = Cryptography.AES_decrypt(sEchoStr, m_sEncodingAESKey, ref cpid); //m_sCorpID);
			}
			catch (Exception)
			{
				sReplyEchoStr = "";
				return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error;
			}
			if (cpid != m_sCorpID)
			{
				sReplyEchoStr = "";
				return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateCorpid_Error;
			}
			return 0;
		}

		// 检验消息的真实性，并且获取解密后的明文
		// @param sMsgSignature: 签名串，对应URL参数的msg_signature
		// @param sTimeStamp: 时间戳，对应URL参数的timestamp
		// @param sNonce: 随机串，对应URL参数的nonce
		// @param sPostData: 密文，对应POST请求的数据
		// @param sMsg: 解密后的原文，当return返回0时有效
		// @return: 成功0，失败返回对应的错误码
		public int DecryptMsg(string sMsgSignature, string sTimeStamp, string sNonce, string sPostData, ref string sMsg)
		{
			if (m_sEncodingAESKey.Length!=43)
			{
				return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
			}
			XmlDocument doc = new XmlDocument();
			XmlNode root;
			string sEncryptMsg;
			try
			{
				doc.LoadXml(sPostData);
				root = doc.FirstChild;
				sEncryptMsg = root["Encrypt"].InnerText;
			}
			catch (Exception)
			{
				return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ParseXml_Error;
			}
			//verify signature
			int ret = 0;
			ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEncryptMsg, sMsgSignature);
			if (ret != 0)
				return ret;
			//decrypt
			string cpid = "";
			try
			{
				sMsg = Cryptography.AES_decrypt(sEncryptMsg, m_sEncodingAESKey, ref cpid);
			}
			catch (FormatException)
			{
				sMsg = "";
				return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecodeBase64_Error;
			}
			catch (Exception)
			{
				sMsg = "";
				return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error;
			}
			if (cpid != m_sCorpID)
				return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateCorpid_Error;
			return 0;
		}

		//将企业号回复用户的消息加密打包
		// @param sReplyMsg: 企业号待回复用户的消息，xml格式的字符串
		// @param sTimeStamp: 时间戳，可以自己生成，也可以用URL参数的timestamp
		// @param sNonce: 随机串，可以自己生成，也可以用URL参数的nonce
		// @param sEncryptMsg: 加密后的可以直接回复用户的密文，包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
		//						当return返回0时有效
		// return：成功0，失败返回对应的错误码
		public int EncryptMsg(string sReplyMsg, string sTimeStamp, string sNonce, ref string sEncryptMsg)
		{
			if (m_sEncodingAESKey.Length!=43)
			{
				return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
			}
			string raw = "";
			try
			{
				raw = Cryptography.AES_encrypt(sReplyMsg, m_sEncodingAESKey, m_sCorpID);
			}
			catch (Exception)
			{
				return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_EncryptAES_Error;
			}
			string MsgSigature = "";
			int ret = 0;
			ret = GenarateSinature(m_sToken, sTimeStamp, sNonce, raw, ref MsgSigature);
			if (0 != ret)
				return ret;
			sEncryptMsg = "";

			string EncryptLabelHead = "<Encrypt><![CDATA[";
			string EncryptLabelTail = "]]></Encrypt>";
			string MsgSigLabelHead = "<MsgSignature><![CDATA[";
			string MsgSigLabelTail = "]]></MsgSignature>";
			string TimeStampLabelHead = "<TimeStamp><![CDATA[";
			string TimeStampLabelTail = "]]></TimeStamp>";
			string NonceLabelHead = "<Nonce><![CDATA[";
			string NonceLabelTail = "]]></Nonce>";
			sEncryptMsg = sEncryptMsg + "<xml>" + EncryptLabelHead + raw + EncryptLabelTail;
			sEncryptMsg = sEncryptMsg + MsgSigLabelHead + MsgSigature + MsgSigLabelTail;
			sEncryptMsg = sEncryptMsg + TimeStampLabelHead + sTimeStamp + TimeStampLabelTail;
			sEncryptMsg = sEncryptMsg + NonceLabelHead + sNonce + NonceLabelTail;
			sEncryptMsg += "</xml>";
			return 0;
		}

		public class DictionarySort : IComparer
		{
			public int Compare(object oLeft, object oRight)
			{
				string sLeft = oLeft as string;
				string sRight = oRight as string;
				int iLeftLength = sLeft.Length;
				int iRightLength = sRight.Length;
				int index = 0;
				while (index < iLeftLength && index < iRightLength)
				{
					if (sLeft[index] < sRight[index])
						return -1;
					else if (sLeft[index] > sRight[index])
						return 1;
					else
						index++;
				}
				return iLeftLength - iRightLength;

			}
		}
		//Verify Signature
		private static int VerifySignature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, string sSigture)
		{
			string hash = "";
			int ret = 0;
			ret = GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt, ref hash);
			if (ret != 0)
				return ret;
			if (hash == sSigture)
				return 0;
			else
			{
				return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateSignature_Error;
			}
		}

		public static int GenarateSinature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt ,ref string sMsgSignature)
		{
			ArrayList AL = new ArrayList();
			AL.Add(sToken);
			AL.Add(sTimeStamp);
			AL.Add(sNonce);
			AL.Add(sMsgEncrypt);
			AL.Sort(new DictionarySort());
			string raw = "";
			for (int i = 0; i < AL.Count; ++i)
			{
				raw += AL[i];
			}

			ASCIIEncoding enc;
			string hash = "";
			try
			{
#if NET45
                SHA1 sha = new SHA1CryptoServiceProvider();
#else
                SHA1 sha = SHA1.Create();
#endif
                enc = new ASCIIEncoding();
				byte[] dataToHash = enc.GetBytes(raw);
				byte[] dataHashed = sha.ComputeHash(dataToHash);
				hash = BitConverter.ToString(dataHashed).Replace("-", "");
				hash = hash.ToLower();
			}
			catch (Exception)
			{
				return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ComputeSignature_Error;
			}
			sMsgSignature = hash;
			return 0;
		}
	}
}
